//
//  KVCViewController.m
//  DebugDemo
//
//  Created by XFB on 2018/11/12.
//  Copyright © 2018年 ZhouCheng. All rights reserved.
//

#import "KVCViewController.h"
#import "People.h"
#import "Persons.h"
#import "Dog.h"
#import "Book.h"

@interface KVCViewController ()

@end

@implementation KVCViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupBasicSet];
    [self setupUI];
}

- (void)setupBasicSet
{
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationItem.title = self.titleString;
}

- (void)setupUI
{
    People *people = [People people];
    /// 通过 setValueForKey 来取值
    NSString *name = [people valueForKey:@"name"];

    ///
    NSLog(@"%@", name);
    
    
    /**
     先找方法，再找变量
     1.先找方法，依次寻找下面方法: -getName -name
     2.如果上面两个方法没有找到，调用 +accessInstanceVariablesDirectly
     如果返回NO 表示不访问变量，直接调用-valueForUndefineKey
     如果返回YES 继续下面不在访问相关变量
     3.访问变量，依次虚招下面变量：_name _isName name isName
     4.如果没找到就调用 -valueForUndefinedKey 【runtime为防止这里崩溃，可以通过其他方法补救】
     
     _<key>, _is<Key>, <key>, is<Key>
     
     调用setValue:属性值 forkey:@"name" 的代码时候，底层执行机制
     1.程序优先调用set<key>方法，通过setter方法完成设置
     2.如果程序没有找到setter方法，KVC机制会检查
     + (BOOL)accessInstanceVariablesDirectly 方法有没有返回YES,该方法默认返回YES
     如果重写了该方法返回NO,那么这一步KVC执行setValue:forUnderfinedKey:方法
     3.如果不想让你的类被别人使用KVC，重写+ (BOOL)accessInstanceVariablesDirectly 方法
     接下来说是该类里面有没有 _<key>的成员变量，无论是在类 .h 部分还是在类 .m 部分定义，也不论用了什么样的访问修饰符
     只要存在 _key 命名的变量，KVC都可以对该成员变量赋值
     4.如果该类中既没有 set<key>方法，也没有<key>成员变量，KVC机制会搜索 _is<key>的成员变量
     5.如果该类还是没有 _<key> 和  _is<key>成员变量， KVC机制再会继续搜索 <key> 和 is<key> 的成员变量（即没有下划线的成员变量），再给他们赋值
     6.如果上面列出的方法或者成员变量都不存在，系统将会执行该对象的 setValue：forUndefined：方法, 默认是抛出异常了
     
     抛出异常处理
     1.重写setValue:forUndefined: 方法
     2.传递 nil 重写setNilValueForKey: 就可以了
     
     当一个对象调用setValue:forKey: 方法时,方法内部会做以下操作:
     1.判断有没有指定key的set方法,如果有set方法,就会调用set方法,给该属性赋值
     2.如果没有set方法,判断有没有跟key值相同且带有下划线的成员属性(_key).如果有,直接给该成员属性进行赋值
     3.如果没有成员属性_key,判断有没有跟key相同名称的属性.如果有,直接给该属性进行赋值
     4.如果都没有,就会调用 valueforUndefinedKey 和setValue:forUndefinedKey:方法
     
     */

    /**
     KVC的使用
     1、动态的取值 和 设值
     2、用KVC来访问对象和修改私有成员变量
     3、Model和字典的转换
     4、修改一些控件的内部属性 UITextField _placeholderLabel.textColor 、_placeholderLabel.font
     
     KVC和点语法比较
     1.点语法编译器会做预编译，访问不存在的属性编译器会报错
     KVC方式编译器无法做检查，如果有错误只能运行的时候才能发现 crash
     2.相比较点语法用KVC方式 KVC的效率会降低一点，但是灵活，可以在程序运行时决定访问哪些属性
     3.KVC可以 访问对象的私有成员变量
     
     KVC实现细节
     - (void)setValue:(id)value forKey:(NSString *)key;
     1.首先说说 Setter 方法，有就直接赋值
     2.如果上面 Setter 方法 没有找到，在检查类方法 + (BOOL)accessInstanceVariableDirectly
     3.返回NO, 则执行 setValue:forUndefinedKey:
     4.返回YES,则按 _<key>、_<isKey>、<key>、<isKey> 的顺序搜索成员名
     5.还没有找到的话，就调用 setValue:forUndefinedKey:
     
     - (id)valueForKey:(NSString *)key;
     1.首先找到 Getter 方法，找到直接调用，如果是 BOOL、int、float 等基本数据类型，会做 NSNumber 的转换
     2.如果没有查到，再检查类方法 + (BOOL)accessInstanceVariableDirectly
     返回NO, 则执行 valueForUndefinedKey:
     返回YES, 则按 _<key>、_<isKey>、<key>、<isKey> 的顺序搜索成员名
     3.还没有找到的话，调用 valueForUndefinedKey:
     
     缺点：
     1.KVC编译器无法检查出错误，即不会对设置的键、键值路径进行错误检查
     2.执行效率低于 Setter 和 Getter 方法。因为使用KVC键值编码，它必须先解析字符串，然后再设置或者访问对象的实例变量
     3.使用KVC会破坏类的封装性
     
     
     
     */
    
    
//    [self test1];
    
//    [self test2];
    
    /// 字典转模型
//    [self test3];
    
    /// 模型转字典
//    [self test4];
    
    /// 访问数组中元素的属性值
    [self test5];
    
}

- (void)test1
{
    
    Persons *p = [[Persons alloc] init];
    //    p.name = @"Tom";
    //    p.money = 50.0;
    //    NSLog(@"%@---%.2f", p.name, p.money);
    /// 使用setValue:forKey: 方法能够给属性赋值，等价于直接给属性赋值
    
    [p setValue:@"Tom" forKey:@"name"];
    [p setValue:@"50.0" forKey:@"money"];
    NSLog(@"%@---%@", p.name, p.money);
    
}

- (void)test2
{
    Persons *p = [[Persons alloc] init];
    p.dog = [[Dog alloc] init];
//    p.dog.name = @"jack";
//    NSLog(@"%@", p.dog.name);
    
    // 1.setValue:forKeyPath: 方法的使用
    /// 修改p.dog 的name属性
//    [p.dog setValue:@"Tom" forKey:@"name"];
    [p setValue:@"jack" forKeyPath:@"dog.name"];
//    NSLog(@"%@", p.dog.name);
    
    // 2.setValue:forKey: 错误用法
//    [p setValue:@"jack" forKey:@"dog.name"];
//    NSLog(@"%@", p.dog.name);
    
    // 3.直接修改 私有成员变量
    [p setValue:@"旺财" forKeyPath:@"_name"];
    
    /// 4,添加私有成员变量
    /// 給Person类添加私有成员变量_age
    [p setValue:@"25" forKeyPath:@"_age"];
 
    NSLog(@"%@", p);
}

- (void)test3
{
    NSDictionary *dict = @{
                           @"name" : @"jack",
                           @"money": @"22.2",
                           @"dog"  : @{
                                         @"name" : @"wangcai",
                                         @"price": @"11.1",
                                         },
                           @"books" : @[
                                   @{
                                       @"name" : @"5分钟放弃iOS开发",
                                       @"price" : @"19.8"
                                       },
                                   @{
                                       @"name" : @"3分钟放弃iOS开发",
                                       @"price" : @"24.8"
                                       },
                                   @{
                                       @"name" : @"1分钟放弃iOS开发",
                                       @"price" : @"29.8"
                                       }
                                   ]
                           };
    
    /// 字典转模型
    Persons *p = [Persons personsModelWithDict:dict];
    NSLog(@"%@--%@--%@--%@", p.name, p.money, p.books, p.dog);
    
}

- (void)test4
{
    Persons *p = [[Persons alloc] init];
    p.name = @"Jack";
    p.money = @"100.0";
    
    /// KVC取值
    NSLog(@"%@---%@", [p valueForKey:@"name"], [p valueForKey:@"money"]);
    /// 模型转字典
    NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]];
    NSLog(@"%@", dict);
}

/// 访问数组中元素的属性值
- (void)test5
{
    Book *book1 = [[Book alloc] init];
    book1.name = @"20分钟放弃JAVA";
    book1.price = @"20.0";
    
    Book *book2 = [[Book alloc] init];
    book2.name = @"10分钟放弃JAVA";
    book2.price = @"40.0";

    Book *book3 = [[Book alloc] init];
    book3.name = @"5分钟放弃JAVA";
    book3.price = @"60.0";
    
    /// 如果valueForKeyPath：方法的调用者是数组。那么就是去访问数组元素的属性值
    /// 取的books数组中所有Book对象的name属性值，放在一个新的数组中返回
    NSArray *books = @[book1, book2, book3];
    NSArray *names = [books valueForKeyPath:@"name"];
//    NSLog(@"%@", names);
    
    /// 访问属性数组中元素的属性值
    Persons *p = [[Persons alloc] init];
    p.books = [NSMutableArray arrayWithObjects:book1, book2, book3, nil];
    NSArray *nameArray = [p valueForKeyPath:@"books.name"];
    NSLog(@"%@", nameArray);
}


@end
